/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.web.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;
import org.tianyun.cloud.common.constants.ResponseCode;
import org.tianyun.cloud.context.AppContext;
import org.tianyun.cloud.exception.BusinessException;
import org.tianyun.cloud.exception.ExceptionContextAware;
import org.tianyun.cloud.exception.SystemException;
import org.tianyun.cloud.web.logger.LogPrinter;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * Controller全局异常处理
 * <p>
 *
 * @author ebert_chan
 */
@ControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler implements InitializingBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    private ExceptionContextAware exceptionContextAware;

    private LogPrinter logPrinter;

    private void checkNull(Throwable e) throws Throwable {
        if (exceptionContextAware == null || logPrinter == null) {
            throw e;
        }
    }

    @ExceptionHandler(value = Throwable.class)
    @ResponseBody
    public Object handleException(Throwable e) throws Throwable {
        this.checkNull(e);
        LOGGER.error("Caused by:", e);
        Object result = exceptionContextAware.feedback(ResponseCode.BUSY_EXCEPTION);
        logPrinter.writeMethodOutParameters(result);
        return result;
    }

    @ExceptionHandler(value = {HttpMediaTypeNotSupportedException.class, HttpMediaTypeNotAcceptableException.class, HttpRequestMethodNotSupportedException.class})
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ResponseBody
    public Object handleHttpMediaTypeNotSupportedException(Exception e, HttpServletRequest request) throws Throwable {
        this.checkNull(e);
        logPrinter.writeRequestInformation(request);
        LOGGER.warn("Caused by: {}", e.getMessage());
        Object result = exceptionContextAware.feedback(ResponseCode.HTTP_MEDIATYPE_ERROR);
        logPrinter.writeMethodOutParameters(result);
        return result;
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseBody
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) throws Throwable {
        this.checkNull(e);
        logPrinter.writeRequestInformation(request);
        String message = null;
        List<ObjectError> errors = e.getBindingResult().getAllErrors();
        if (errors != null && errors.size() > 0) {
            message = errors.get(0).getDefaultMessage();
        }
        Object result = null;
        if (message != null) {
            result = exceptionContextAware.feedback(ResponseCode.PARAMETER_VALIDATE_EXCEPTION, message);
        } else {
            result = exceptionContextAware.feedback(ResponseCode.ERROR_INPUT);
        }
        logPrinter.writeMethodOutParameters(result);

        return result;
    }

    @ExceptionHandler({MissingServletRequestParameterException.class})
    @ResponseBody
    public Object handleMethodArgumentNotValidException(MissingServletRequestParameterException e, HttpServletRequest request) throws Throwable {
        this.checkNull(e);
        logPrinter.writeRequestInformation(request);
        LOGGER.warn("Caused by: {}", e.getMessage());
        Object result = exceptionContextAware.feedback(ResponseCode.PARAMETER_IS_REQUIRED);
        logPrinter.writeMethodOutParameters(result);
        return result;
    }

    @ExceptionHandler(value = {MultipartException.class})
    @ResponseBody
    public Object handleMultipartException(MultipartException e, HttpServletRequest request) throws Throwable {
        this.checkNull(e);
        logPrinter.writeRequestInformation(request);
        LOGGER.warn("Caused by: {}", e.getMessage());
        Object result = exceptionContextAware.feedback(ResponseCode.ERROR_INPUT);
        logPrinter.writeMethodOutParameters(result);
        return result;
    }

    @ExceptionHandler(value = {BindException.class})
    @ResponseBody
    public Object handleBindException(BindException e, HttpServletRequest request) throws Throwable {
        this.checkNull(e);
        logPrinter.writeRequestInformation(request);
        String message = null;
        List<ObjectError> errors = e.getBindingResult().getAllErrors();
        if (errors != null && errors.size() > 0) {
            message = errors.get(0).getDefaultMessage();
        }
        Object result = null;
        if (message != null) {
            result = exceptionContextAware.feedback(ResponseCode.PARAMETER_VALIDATE_EXCEPTION, message);
        } else {
            result = exceptionContextAware.feedback(ResponseCode.ERROR_INPUT);
        }
        logPrinter.writeMethodOutParameters(result);

        return result;
    }

    @ExceptionHandler({MethodArgumentTypeMismatchException.class, HttpMessageNotReadableException.class})
    @ResponseBody
    public Object handlerErrorInput(Exception e, HttpServletRequest request) throws Throwable {
        this.checkNull(e);
        logPrinter.writeRequestInformation(request);
        LOGGER.warn("Caused by: {}", e.getMessage());
        Object result = exceptionContextAware.feedback(ResponseCode.ERROR_INPUT);
        logPrinter.writeMethodOutParameters(result);
        return result;
    }

    @ExceptionHandler(value = {BusinessException.class})
    @ResponseBody
    public Object handleBusinessException(BusinessException e) throws Throwable {
        this.checkNull(e);
        LOGGER.warn(e.getMessage());
        Object result = exceptionContextAware.feedback(e.getCode(), e.getArgs());
        logPrinter.writeMethodOutParameters(result);
        return result;
    }

    @ExceptionHandler(value = {SystemException.class})
    @ResponseBody
    public Object handleSystemException(SystemException e) throws Throwable {
        this.checkNull(e);
        LOGGER.error("Caused by:", e);
        Object result = exceptionContextAware.feedback(ResponseCode.SYSTEM_EXCEPTION, e.getCode());
        logPrinter.writeMethodOutParameters(result);
        return result;
    }

    /*
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        try {
            exceptionContextAware = AppContext.getBean(ExceptionContextAware.class);
            logPrinter = AppContext.getBean(LogPrinter.class);
        } catch (Throwable e) {
            // do nothing
            LOGGER.warn("'GlobalExceptionHandler' is not working. caused by: {}", e.getMessage());
        }
    }

}
